#pragma once
#include "inet.hpp"
#include "Log.h"
#include "thread_pool.h"
#include <map>

class thread_data;               // 提前声明
#define default_backlog 5        // 全连接队列
using task_t = function<void()>; // 包装器，无参无返回值
using func_t = function<void(thread_data)>;

class thread_data // 线程对应的套接字描述符
{
public:
    int _sockfd;
    Inet_addr _inet;
    thread_data(int sockfd, Inet_addr tmp) : _sockfd(sockfd), _inet(tmp)
    {
    }
    ~thread_data()
    {
        // close(_sockfd);不能在这里关闭，因为线程的生命周期与thread_data对象不同步
    }
};

class tcp_server
{
public:
    tcp_server(uint16_t port)
        : _port(port)
    {
    }
    void inite()
    {
        // 1.创建套接字
        _listen_sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (_listen_sockfd == -1)
        {
            lg.Log_infom(Fatal, "创建套接字失败error:%d,strerrno:%s,_listen_sockfd = %d", errno, strerror(errno), _listen_sockfd);
            exit(-1);
        }
        lg.Log_infom(Debug, "创建套接字成功,sockfd = %d", _listen_sockfd);

        // 解决一些服务端绑定失败无法重启的问题
        int opt = 1;
        setsockopt(_listen_sockfd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));

        // 2.绑定网络信息
        struct sockaddr_in local;
        local.sin_family = AF_INET;
        local.sin_addr.s_addr = INADDR_ANY; // 宏值就是0
        local.sin_port = htons(_port);      // 端口号没绑好就会出错
        int n = ::bind(_listen_sockfd, (sockaddr *)&local, sizeof(local));
        if (n != 0)
        {
            lg.Log_infom(Fatal, "绑定网络信息失败error:%d,strerrno:%s,bind_ret = %d", errno, strerror(errno), n);
            exit(-1);
        }
        lg.Log_infom(Debug, "绑定网络信息成功,bind_ret = %d", n);

        // 3.客户端发起连接，服务器等待连接，将套接字设置为监听状态
        n = listen(_listen_sockfd, default_backlog);
        if (n == -1)
        {
            lg.Log_infom(Fatal, "监听套接字失败error:%d,strerrno:%s,listensocket = %d", errno, strerror(errno), n);
            exit(-1);
        }
        lg.Log_infom(Debug, "监听套接字成功,bind_ret = %d", n);

        // 创建线程池
        thread_pool<task_t>::get_instance();
    }

    void Service(int sockfd) // （用于v1和v2）
    {
        while (1)
        {
            char buffer[1024] = {0};
            int n = read(sockfd, buffer, sizeof(buffer) - 1);

            if (n > 0)
            {
                buffer[n] = 0;
                lg.Log_infom(Debug, "server recieve info:%s", buffer);
            }
            else if (n == 0) // 读到文件末尾
            {
                lg.Log_infom(Info, "数据已经全部读取完毕...");
                break;
            }
            else
            {
                lg.Log_infom(Error, "数据读取失败");
                break;
            }

            write(sockfd, buffer, strlen(buffer)); // sizeof此时大小还是1024
            cout << "send info: " << buffer << endl;
        }
    }
    void Service(thread_data tmp) // 重载(v3多线程使用)
    {
        while (1)
        {
            char buffer[1024] = {0};
            int n = read(tmp._sockfd, buffer, sizeof(buffer) - 1);

            if (n > 0)
            {
                buffer[n] = 0;
                tmp._inet.print_client_info(buffer);
            }
            else if (n == 0) // 读到文件末尾
            {
                lg.Log_infom(Info, "数据已经全部读取完毕...");
                break;
            }
            else
            {
                lg.Log_infom(Error, "数据读取失败");
                break;
            }

            write(tmp._sockfd, buffer, strlen(buffer)); // sizeof此时大小还是1024
            cout << "send info: " << buffer << endl;
        }
    }

    void handler(thread_data tmp)
    {
        Service(tmp); // 内部是while循环，在v4线程池是，将线程与用户一一分配了，当客户端>线程个数就无法输入
        close(tmp._sockfd);
    }

    void start()
    {
        while (1)
        {
            // 4.服务端获取客户端连接
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            int sockfd = accept(_listen_sockfd, (sockaddr *)&client, &len); // 没连接一次都会返回一个新的sockfd负责接下来的通信

            if (sockfd < 0)
            {
                lg.Log_infom(Warning, "服务端获取连接失败error:%d,strerrno:%s,newsockedt = %d", errno, strerror(errno), sockfd);
                continue; // 获取连接失败继续获取
            }
            lg.Log_infom(Debug, "服务端获取连接成功,newsocket = %d", sockfd);

            ////***v1:一般模式
            // 5.提供服务进行通信
            // Service(sockfd);

            // 关闭文件
            // close(sockfd);

            ////***v2:创建子进程，父进程进行获取不同客户端的连接，子进程进行通信(此时就可以多客户端通信)
            // 此时有3、4号文件描述符(父子共享)

            // signal(SIGCHLD,SIG_IGN);//在linux环境中,对该信号进行忽略则表明在子进程退出的时候，就会自动释放资源
            // pid_t id = fork(); // 创建子进程
            // if (id == -1)
            // {
            //     lg.Log_infom(Error, "创建子进程失败,fork_ret=%d", id);
            //     close(sockfd);
            //     continue;
            // }

            // a.创建孙子进程的方式
            //  else if (id == 0) // 子进程
            //  {
            //      close(_listen_sockfd);
            //      if (fork() > 0) // 执行完毕后退出当前进程（子进程）
            //          exit(0);
            //      // 接下来就是孙子进程所执行的代码
            //      Service(sockfd); // 孙子进程的父进程已经退出了，所以被OS领养回收资源
            //      exit(0);
            //  }
            //  else if (id > 0)
            //  {
            //      close(sockfd);
            //      // 等待子进程退出
            //      pid_t ret = waitpid(id, nullptr, 0);
            //      if (ret == id)
            //          ;
            //  }

            // b.采用父进程不等待的方式,而是信号的方式
            //  else if (id == 0) // 子进程
            //  {
            //      close(_listen_sockfd);
            //      Service(sockfd); // 孙子进程的父进程已经退出了，所以被OS领养回收资源
            //      exit(0);
            //  }
            //  else if (id > 0)
            //  {
            //      close(sockfd);
            //  }

            ////***v3创建多线程(父进程不断地循环等待连接，每个线程（取决于客户端申请连接）执行自己的任务)
            // thread_data tmp(sockfd,Inet_addr(client));//第二个参数存放发送者的套接字信息

            // thread t(std::bind(&tcp_server::handler,this,placeholders::_1),tmp);
            // //此时父进程执行后续代码，可能会再进行一次accept获取连接，那么sockfd的值可能会改变

            // t.detach();//线程分离，父进程不用等待回收

            ////***v4线程池（提前将线程创建好,主线程进行任务的接收并存入线程池的任务栏，子线程进行任务处理）
            // thread_data tmp(sockfd, Inet_addr(client)); // 第一个参数是每个线程对应的sockfd,第二个参数存放发送者的套接字信息
            // task_t t = std::bind(&tcp_server::handler, this, tmp);
            // thread_pool<task_t>::get_instance()->push(t); // 将任务压入进程池的任务栏

            ////***v4.2线程池执行任务
            thread_data tmp(sockfd, Inet_addr(client)); // 第一个参数是每个线程对应的sockfd,第二个参数存放发送者的套接字信息
            task_t t = std::bind(&tcp_server::routine, this, tmp);
            thread_pool<task_t>::get_instance()->push(t); // 将任务压入进程池的任务栏
        }
    }
    void registr(string s, func_t f) // 将任务提前登记
    {
        _mapfunc[s] = f;
    }
    void routine(thread_data tmp) // 线程接收客户端发送的任务种类，并进行处理，代替handler下的service功能
    {
        //读取任务种类
        char buffer[1024] = {0};
        int n = read(tmp._sockfd, buffer, sizeof(buffer) - 1);
        string s;
        if (n > 0)
        {
            buffer[n] = 0;
            s=buffer;
        }
        else if (n == 0) // 读到文件末尾
        {
            lg.Log_infom(Info, "数据已经全部读取完毕...");
        }
        else
        {
            lg.Log_infom(Error, "数据读取失败");
        }
        //子进程判断任务并执行
        if (s=="ping")
            _mapfunc[s](tmp);
        else if(s=="translate")
            _mapfunc[s](tmp);
        else if(s=="transform")
            _mapfunc[s](tmp);
        else
            _mapfunc["default_func"](tmp);
        
        close(tmp._sockfd);//线程执行完毕就关闭
    }
    ~tcp_server()
    {
    }

private:
    uint16_t _port;
    int _listen_sockfd;
    map<string, func_t> _mapfunc;
};